package core;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

import request.GameRequest;

/**
 * Each gameclient will be treated as one thread and keep the connection
 * persistent. Request and response will be handled separately.
 *
 * @author yiyi, Zoran, Abra, Gary, Kaven
 *
 */
public class GameClient extends Thread {

    // Socket from client request.
    private Socket mySocket;
    // Reference to game server.
    private GameServer gameServer;
    // User sending this request.
    private GameUser user;
    // Outputstream from client socket
    private OutputStream output;
    // Inputdatastream from client socket
   // private DataInputStream input;
    private DataInputStream input;
    //private DataInputStream data;
    private DataInputStream data;
    private InputStream it;
    private boolean isRunning;

    /**
     * Constructor of GameClient
     *
     * @param incoming      the client socket
     * @param server        main game Server reference
     */
    public GameClient(Socket incoming, GameServer server) {
        mySocket = incoming;
        gameServer = server;

        try {
            output = mySocket.getOutputStream();
            //input = new DataInputStream((mySocket.getInputStream()));
            input = new DataInputStream((mySocket.getInputStream()));
            /**
            short size = readShort(input);
            System.out.println("SIZE IS: "+size);
            short code = readShort(input);
            System.out.println("CODE IS: "+code);
            short sizeofversion = readShort(input);
            System.out.println("SIZE OF VERSION  IS: "+sizeofversion);
            byte[] aString = new byte[sizeofversion];
            String version="";
            try {
                input.read(aString, 0, sizeofversion);
                version=new String(aString,0,sizeofversion);
                //str =in.readUTF();//in.readUTF();
            } catch (IOException e) {
            }
            System.out.println("VERSION  IS: "+version);**/
        } catch (IOException e) {
            gameServer.log(e.getMessage());
        }
    }

    /**
     * Generic function read in one string from the DatainputStream
     * Used for Python client pydatagram decoding
     *
     * @param in	datainputstream from client
     * @return		a single string
     */
    public String readString(DataInputStream in) {
        
            int length = readShort(in);
            byte[] aString = new byte[length];
            
            try {
                in.read(aString, 0, length);
                
            } catch (IOException e) {
                gameServer.log(e.getMessage());
            }
            
            return (new String(aString, 0, length));
            
       
        
        
    }

    /**
     * Generic function read in one float from the DatainputStream
     * Used for Python client pydatagram decoding
     *
     * @param in		datainputstream from client
     * @param length	the length of the float number
     * @return		 	a float number
     */
    public float readFloat(DataInputStream in) {
        try {
            if (in.available() > 0) {
                return Float.intBitsToFloat(Integer.reverseBytes(in.readInt()));
            }
        } catch (IOException e) {
            gameServer.log(e.getMessage());
        }

        return -1;
    }

    /**
     * Generic function read in one short from the DatainputStream
     * Used for Python client pydatagram decoding
     *
     * @param in	datainputstream from client
     * @return		a single short number
     */
    public short readShort(DataInputStream in) {
        try {
            //if (in.available() > 0) {
                return Short.reverseBytes(in.readShort());
            //}
        } catch (IOException e) {
            gameServer.log(e.getMessage());
        }

        return -1;
    }

    public int readInt(DataInputStream in) {
        try {
            if (in.available() > 0) {
                return Integer.reverseBytes(in.readInt());
            }
        } catch (IOException e) {
            gameServer.log(e.getMessage());
        }

        return -1;
    }

    public boolean readBoolean(DataInputStream in) {
        try {
            if (in.available() > 0) {
                return in.readBoolean();
            }
        } catch (IOException e) {
            gameServer.log(e.getMessage());
        }

        return false;
    }

    /**
     * Start the client thread
     */
    @Override
    public void run() {
        // Number of cycles in which client's thread will sleep.
        // Each sleeping cycle represents 20 mSec of client's inactivity.
        // Server, meaning GameClient's thread, will wait for 1 (one) minute
        // for client to send message to Server. After 1 minute of inactivity,
        // that GameClient's thread will terminate.
        int number_of_sleep_cycles = 0;

        try {
            isRunning = true;

            while (isRunning) {
                try {
                    // In pydatagram, the first short is always size of package
                    short size = readShort(input);
                    
                    if (size > 0) {
                        // If request received, reset number_of_sleep_cycles.
                        number_of_sleep_cycles = 0;

                        //byte[] buffer = new byte[size];
                        //input.read(buffer,0,size);
                        //data = new DataInputStream(new ByteArrayInputStream(buffer));
                        //data = new DataInputStream(new ByteArrayInputStream(buffer));
                        data=input;
                        try {
                            // Protocol defined the second short must be request CODE
                            short requestCode = readShort(data);
                            gameServer.log("Server: Reading client request with code: "+requestCode,"d");
                            if (requestCode != -1) {
                                // Create request according to the code
                                GameRequest request = GameRequestTable.get(requestCode);
                                if (request != null) {
                                    if (GameServer.debugMode && requestCode != Constants.CMSG_HEART && requestCode != Constants.CMSG_MOVE) {
                                        System.out.println("Q[" + requestCode + "]: " + request.getClass());
                                    }

                                    // Setup the game request.
                                    request.set(size - 4, data, this, gameServer);
                                    // Parse the request
                                    request.parse();
                                    // Generate response
                                    request.respond(output);
                                    
                                    
                                    if (requestCode == Constants.CMSG_AUTH) {
                                        if (user != null) {
                                            user.setClientID(this.getId());
                                        }
                                    }
                                    /*DEBUG
                                     else if (requestCode == Constants.CMSG_DISCONNECT) {
                                        gameServer.removeClientThread(this.getId());
                                        return;
                                    }*/
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            gameServer.log(e.getMessage());
                        }
                    } else {
                        // If size of package is <= 0 (no valid request)
                        // Temporary solution for over-utilization of CPU when client is idle
                        // This line should put thread to sleep, for 20 milliseconds each time,
                        // for as long as client sends no requests.
                        Thread.sleep(20);
                        number_of_sleep_cycles++;

                        // Check if GameClient has been waiting for client's request
                        // at least 1 minute (= 3,000 sleeping cycles)
                       if (number_of_sleep_cycles >= 21000) {
                        isRunning = false;
                            /*man-
                            if (user != null) {
                                if (user.getID() == Constants.BUG_USER_ID) {
                                    gameServer.restartBugServerMonitor();
                                }
                            }*/
                     }
                    }
                } catch (InterruptedException e) {
                    gameServer.log(e.getMessage());
                }
            }
            
            if (user != null) {
                gameServer.logout(user);
            }

            gameServer.removeClientThread(this.getId());
        } catch (Exception e) {
            
            gameServer.log(e.getMessage());
        }
    }

    public GameUser getUser() {
        return user;
    }

    public void setUser(GameUser user) {
        this.user = user;
    }

    public String getClientIP() {
        return mySocket.getInetAddress().getHostAddress();
    }

    public OutputStream getOutput() {
        return output;
    }

    public void end() {
        isRunning = false;
    }
}
